import * as step from '@flow-step/step-toolkit'
import {oss} from '@flow-step/step-toolkit'
import * as glob from 'glob';
import * as fs from 'fs'
import * as path from 'path'
import {RemoteStorage} from "@flow-step/step-toolkit/src/oss";
import process from 'process'
import proxyquire from 'proxyquire'
import * as install from '../src/install'
import { getOssEndpoint } from '../src/ossUpload';
import axios from 'axios';
import sinon from 'sinon';
import { expect } from 'chai';
import { upload,uploadWithMeta } from '../src/ossUpload';
import {exec} from 'child_process'
import {installOssutil} from '../src/install' // Adjust this import to match your project structure


describe('upload', () => {

    let remoteStorage: RemoteStorage;
    let globStub: sinon.SinonStub;
    let installOssutil:  sinon.SinonStub;
    let stepExecStub: sinon.SinonStub;

    const localPath = 'path/to/local';
    const ossPath = 'path/to/oss';
    const suffix = 'txt';
    const meta = 'Cache-Control:no-cache#Content-Encoding:gzip';

    let fsStub = {
        statSync: sinon.stub(),
        existsSync: sinon.stub(),
        appendFile: sinon.stub(),
    };


    let stepStub = {
        certificate: {
            getOssRamServiceConnectionCertificate: sinon.stub(),
        },
        oss: {
            OSSStorage: sinon.stub(),
            uploadWithUpdate: sinon.stub()
        },
        info: sinon.stub(),
        success: sinon.stub(),
        error: sinon.stub(),
        warning: sinon.stub(),
        interaction:{
            addLink:sinon.stub(),
        }
    };


    beforeEach(() => {
        // Mock environment variables
        process.env.UPLOAD_PATH = 'some/upload/path';
        process.env.PROJECT_DIR = '/project/dir';
        process.env.OSS_PATH = '/oss/path';
        process.env.FLOW_JOB_TOKEN = 'token';
        process.env.SERVICE_CONNECTION_ID = '123456';
        process.env.OSS_REGION = 'ucn-hangzhou';
        process.env.OSS_BUCKET = 'my-bucket';
        process.env.WORK_SPACE = '/workspace';
        process.env.METAS = JSON.stringify([{ key: suffix, value: meta }]);

        // Create stubs for dependencies
        globStub = sinon.stub(glob, 'glob');
        fsStub.existsSync.returns(true);
        fsStub.statSync.returns({ mtime: new Date() });
        fsStub.appendFile.resolves();
        stepStub.certificate.getOssRamServiceConnectionCertificate.resolves({ accessKeyId: 'akid', accessKeySecret: 'secret', securityToken: 'token' });
        remoteStorage = {
            head: sinon.stub(),
            get: sinon.stub(),
            put: sinon.stub(),
            stat: sinon.stub(),
        };
        stepStub.oss.OSSStorage.returns(remoteStorage);
        const files = [`file1.txt`, `d2/file2.txt`];
        globStub.resolves(files);

        stepExecStub = sinon.stub(step.exec, 'call').resolves(0);

    });

    afterEach(() => {
        sinon.restore(); // Restore all stubs
    });

    it('should upload files when conditions are met', async () => {
        fsStub.statSync.returns({isFile:() => false});
        const uploadProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
            '@flow-step/step-toolkit': stepStub,
        }).upload;
        await uploadProxy();

        expect(stepStub.success.calledTwice).to.be.true; // For success messages
        expect(stepStub.certificate.getOssRamServiceConnectionCertificate.calledOnce).to.be.true;
        expect(stepExecStub.called).to.be.true;
        expect(stepStub.interaction.addLink.calledOnce).to.be.true;
    });

    it('should upload a file when the path is a file path', async () => {
        fsStub.statSync.returns({isFile:() => true});
        const uploadProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
            '@flow-step/step-toolkit': stepStub,
        }).upload;
        await uploadProxy();

        expect(stepExecStub.calledWith('ossutil', sinon.match.any)).to.be.true;
    });

    it('should throw an error if the source file path does not exist', async () => {
        fsStub.existsSync.returns(false);
        fsStub.statSync.returns({isFile:() => false});
        const uploadProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
            '@flow-step/step-toolkit': stepStub,
        }).upload;

        try {
            await uploadProxy();
        }catch (e:any){
            expect(e.message).to.equal('sourceFilePath /project/dir/some/upload/path does not exist');
        }
    });

    it('should warn if no files in local path', async () => {
        globStub.resolves([]);
        fsStub.statSync.returns({isFile:() => false});
        const uploadProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
            '@flow-step/step-toolkit': stepStub,
        }).upload;

        await uploadProxy();
        expect(stepStub.warning.calledWith('all files already uploaded or user dir is empty, skip uploading')).to.be.true;

    });

    it('should throw an error if FLOW_JOB_TOKEN is missing', async () => {
        delete process.env.FLOW_JOB_TOKEN;
        fsStub.statSync.returns({isFile:() => false});
        const uploadProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
            '@flow-step/step-toolkit': stepStub,
        }).upload;

        try {
            await uploadProxy();
        }catch (e:any){
            expect(e.message).to.equal('missing FLOW_JOB_TOKEN');
        }
    });

    it('should handle error when uploading files', async () => {
        stepExecStub.rejects(new Error('Upload error'))
        fsStub.statSync.returns({isFile:() => false});
        const uploadProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
            '@flow-step/step-toolkit': stepStub,
        }).upload;

        try {
            await uploadProxy();
        }catch (e:any){
            expect(e.message).to.equal('Upload error');
            expect(stepStub.error.calledOnceWith('Failed to upload files to oss'))
        }

    });
});


describe('uploadWithMeta', () => {
    let client: RemoteStorage;
    let globStub: sinon.SinonStub;
    let uploadWithUpdateStub: sinon.SinonStub;
    let stepInfoStub: sinon.SinonStub;
    let stepWarningStub: sinon.SinonStub;
    let stepErrorStub: sinon.SinonStub;

    const localPath = 'path/to/local';
    const ossPath = 'path/to/oss';
    const suffix = 'txt';
    const meta = 'Cache-Control:no-cache#Content-Encoding:gzip';

    const fsStub = {
        statSync: sinon.stub(),
    };

    beforeEach(() => {
        client = {
            head: sinon.stub(),
            get: sinon.stub(),
            put: sinon.stub(),
            stat: sinon.stub(),
        }; // Mock RemoteStorage client

        // Stub glob.glob
        globStub = sinon.stub(glob, 'glob');

        // Stub step.oss.uploadWithUpdate
        uploadWithUpdateStub = sinon.stub(oss, 'uploadWithUpdate');

        // Stub step.info, step.warning, step.error
        stepInfoStub = sinon.stub(step, 'info');
        stepWarningStub = sinon.stub(step, 'warning');
        stepErrorStub = sinon.stub(step, 'error');
    });

    afterEach(() => {
        sinon.restore();
    });


    it('should upload files and log success when files are found', async () => {
        const files = [`file1.txt`, `d2/file2.txt`];
        globStub.resolves(files);

        // Mock fs.statSync to return a mock mtime
        fsStub.statSync.returns({ mtime: new Date(),
                                        isFile:() => false
        });

        // Mock uploadWithUpdate to resolve
        uploadWithUpdateStub.resolves();

        const uploadWithMetaProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
        }).uploadWithMeta;

        await uploadWithMetaProxy(client, localPath, ossPath, suffix, meta);

        let expectHeaders = {
            'Cache-Control': 'no-cache',
            'Content-Encoding': 'gzip'
        }

        expect(globStub.calledOnce).to.be.true;
        expect(stepInfoStub.calledWith(`successfully upload files suffixed with ${suffix} and add meta info ${meta}`)).to.be.true;
        //expect(uploadWithUpdateStub.callCount).to.equal(2); // Two files
        expect(uploadWithUpdateStub.calledWith(client,`path/to/local/file1.txt`,'path/to/oss/file1.txt',expectHeaders)).to.be.true;
        expect(uploadWithUpdateStub.calledWith(client,`path/to/local/d2/file2.txt`,'path/to/oss/d2/file2.txt',expectHeaders)).to.be.true;
    });

    it('should skip upload and log a warning if no files are found', async () => {
        globStub.returns(Promise.resolve([]));

        fsStub.statSync.returns({isFile:() => false});

        const uploadWithMetaProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
        }).uploadWithMeta;

        await uploadWithMetaProxy(client, localPath, ossPath, suffix, meta);

        expect(stepWarningStub.calledWith(`Could not find files suffixed with ${suffix}, skip`)).to.be.true;
    });

    it('should log an error and throw if upload fails', async () => {
        const files = [`${localPath}/file1.txt`];
        globStub.returns(Promise.resolve(files));

        fsStub.statSync.returns({ mtime: new Date(),
            isFile:() => false
        });
        uploadWithUpdateStub.rejects(new Error('Upload failed'));

        const uploadWithMetaProxy = proxyquire('../src/ossUpload', {
            fs: fsStub,
        }).uploadWithMeta;

        try {
            await uploadWithMetaProxy(client, localPath, ossPath, suffix, meta);
        } catch (err:any) {
            expect(err.message).to.equal('Upload failed');
            expect(stepErrorStub.calledWith(`failed to upload files suffixed with ${suffix} and add meta info ${meta}`)).to.be.true;
        }
    });

});

describe('getEndpoint Function', () => {
    let axiosGetStub: sinon.SinonStub;

    beforeEach(() => {
        // Mock axios.get 方法
        axiosGetStub = sinon.stub(axios, 'get');
    });

    afterEach(() => {
        // 恢复原始方法
        sinon.restore();
    });


    it('should return beijing internet  check connection 500', async () => {
        const endpoint = 'https://oss-cn-beijing.aliyuncs.com';
        process.env['OSS_REGION'] = 'cn-beijing';
        axiosGetStub.rejects({ status: 500 } );

        const result = await getOssEndpoint();
        expect(result).to.equal(endpoint);
    });
    it('should return beijing internet  check connection 403', async () => {
        const endpoint = 'https://oss-cn-beijing-internal.aliyuncs.com';
        process.env['OSS_REGION'] = 'cn-beijing';
        axiosGetStub.rejects({ status: 403 } );

        const result = await getOssEndpoint();
        expect(result).to.equal(endpoint);
    });
    it('should return beijing internet  check connection 200', async () => {
        const endpoint = 'https://oss-cn-beijing-internal.aliyuncs.com';
        process.env['OSS_REGION'] = 'cn-beijing';
        axiosGetStub.returns({ status: 200 } );

        const result = await getOssEndpoint();
        expect(result).to.equal(endpoint);
    });

});
